<?php
/*
created by @Zac
 */
// 状态相关
define('STAGE_INIT', 0);
define('STAGE_ADDR', 1);
define('STAGE_UDP_ASSOC', 2);
define('STAGE_DNS', 3);
define('STAGE_CONNECTING', 4);
define('STAGE_STREAM', 5);
define('STAGE_DESTROYED', -1);
// 命令
define('CMD_CONNECT', 1);
define('CMD_BIND', 2);
define('CMD_UDP_ASSOCIATE', 3);
//https://shadowsocks.org/en/spec/protocol.html 协议说明
//parse_header 请求地址类型
define('ADDRTYPE_IPV4', 1);//IPv4
define('ADDRTYPE_IPV6', 4);//IPv6
define('ADDRTYPE_HOST', 3);//Remote DNS


/*
原始包头格式
+--------------+---------------------+------------------+----------+
| Address Type | Destination Address | Destination Port |   Data   |
+--------------+---------------------+------------------+----------+
|      1       |       Variable      |         2        | Variable |
+--------------+---------------------+------------------+----------+
*/
// ss OTA 相关
//https://shadowsocks.org/en/spec/one-time-auth.html
define('ONETIMEAUTH_BYTES', 10);            //OTA hash长度
define('ONETIMEAUTH_CHUNK_BYTES', 12);    //数据流阶段每个数据包 OTA 所占用长度
define('ONETIMEAUTH_CHUNK_DATA_LEN', 2);    //OTA data.len 长度
define('ADDRTYPE_AUTH', 0x10);//即是0b00010000
//define('ADDRTYPE_MASK', 0xF);//即是0b00011111

/**
 * 加密解密类
 * @author walkor<walkor@workerman.net>
 */
class Encryptor
{

    protected $password;

    public $_key;

    protected $_method;

    protected $_cipher;
    /**
     * @var Decipher
     */
    protected $_decipher;

    protected $_bytesToKeyResults = array();

    protected static $_cachedTables = array();

    protected static $_encryptTable = array();

    protected static $_decryptTable = array();

    protected $_cipherIv;//我方发出去的数据的加密向量

    public $_decipherIv;//对方发来的数据的加密向量

    protected $_ivSent;

    protected static $_methodSupported = array(
        'aes-128-cfb' => array(16, 16),
        'aes-192-cfb' => array(24, 16),
        'aes-256-cfb' => array(32, 16),
        'bf-cfb' => array(16, 8),
        'camellia-128-cfb' => array(16, 16),
        'camellia-192-cfb' => array(24, 16),
        'camellia-256-cfb' => array(32, 16),
        'cast5-cfb' => array(16, 8),
        'des-cfb' => array(8, 8),
        'idea-cfb' => array(16, 8),
        'rc2-cfb' => array(16, 8),
        'rc4' => array(16, 0),
        'rc4-md5' => array(16, 16),
        'seed-cfb' => array(16, 16));

    public static function initTable($key)
    {
        $_ref = self::getTable($key);
        self::$_encryptTable = $_ref[0];
        self::$_decryptTable = $_ref[1];
    }

    public function __construct($key, $method)
    {
        $this->password = $key;
        $this->_method = $method;
        if ($this->_method == 'table') {
            $this->_method = null;
        }
        if ($this->_method) {
            $iv_size = openssl_cipher_iv_length($this->_method);
            $iv = openssl_random_pseudo_bytes($iv_size);
            $this->_cipher = $this->getcipher($this->password, $this->_method, 1, $iv);
        } else {
            if (!self::$_encryptTable) {
                $_ref = self::getTable($this->password);
                self::$_encryptTable = $_ref[0];
                self::$_decryptTable = $_ref[1];
            }
        }
    }

    protected static function getTable($key)
    {
        if (isset(self::$_cachedTables[$key])) {
            return self::$_cachedTables[$key];
        }
        $int32Max = pow(2, 32);
        $table = array();
        $decrypt_table = array();
        $hash = md5($key, true);
        $tmp = unpack('V2', $hash);
        $al = $tmp[1];
        $ah = $tmp[2];
        $i = 0;
        while ($i < 256) {
            $table[$i] = $i;
            $i++;
        }
        $i = 1;
        while ($i < 1024) {
            $table = merge_sort($table,
                function ($x, $y) use ($ah, $al, $i, $int32Max) {
                    return (($ah % ($x + $i)) * $int32Max + $al) % ($x + $i) - (($ah % ($y + $i)) * $int32Max + $al) % ($y + $i);
                });
            $i++;
        }
        $table = array_values($table);
        $i = 0;
        while ($i < 256) {
            $decrypt_table[$table[$i]] = $i;
            ++$i;
        }
        ksort($decrypt_table);
        $decrypt_table = array_values($decrypt_table);
        $result = array($table, $decrypt_table);
        self::$_cachedTables[$key] = $result;
        return $result;
    }

    public static function substitute($table, $buf)
    {
        $i = 0;
        $len = strlen($buf);
        while ($i < $len) {
            $buf[$i] = chr($table[ord($buf[$i])]);
            $i++;
        }
        return $buf;
    }

    protected function getCipher($password, $method, $op, $iv)
    {
        $method = strtolower($method);
        $m = $this->getCipherLen($method);
        if ($m) {
            $ref = $this->EVPBytesToKey($password, $m[0], $m[1]);
            $key = $ref[0];
            $iv_ = $ref[1];
            $this->_key = $key;
            if ($iv == null) {
                $iv = $iv_;
            }
            $iv = substr($iv, 0, $m[1]);
            if ($op === 1) {
                $this->_cipherIv = $iv;
            } else {
                $this->_decipherIv = $iv;
            }
            if ($method === 'rc4-md5') {
                return $this->createRc4Md5Cipher($key, $iv, $op);
            } else {
                if ($op === 1) {
                    return new Encipher($method, $key, $iv);
                } else {
                    return new Decipher($method, $key, $iv);
                }
            }
        }
        return null;
    }

    public function encrypt($buffer)
    {
        if ($this->_method) {
            $result = $this->_cipher->update($buffer);
            if ($this->_ivSent) {
                return $result;
            } else {
                $this->_ivSent = true;
                return $this->_cipherIv . $result;
            }
        } else {
            return self::substitute(self::$_encryptTable, $buffer);
        }
    }

    public function decrypt($buffer)
    {
        if ($this->_method) {
            if (!$this->_decipher) {
                $decipher_iv_len = $this->getCipherLen($this->_method);
                $decipher_iv_len = $decipher_iv_len[1];
                $decipher_iv = substr($buffer, 0, $decipher_iv_len);
                $this->_decipher = $this->getCipher($this->password, $this->_method, 0, $decipher_iv);
                $result = $this->_decipher->update(substr($buffer, $decipher_iv_len));
                return $result;
            } else {
                $result = $this->_decipher->update($buffer);
                return $result;
            }
        } else {
            return self::substitute(self::$_decryptTable, $buffer);
        }
    }

    protected function createRc4Md5Cipher($key, $iv, $op)
    {
        $rc4_key = md5($key . $iv);
        if ($op === 1) {
            return new Encipher('rc4', $rc4_key, '');
        } else {
            return new Decipher('rc4', $rc4_key, '');
        }
    }

    protected function EVPBytesToKey($password, $key_len, $iv_len)
    {
        $cache_key = "$password:$key_len:$iv_len";
        if (isset($this->_bytesToKeyResults[$cache_key])) {
            return $this->_bytesToKeyResults[$cache_key];
        }
        $m = array();
        $i = 0;
        $count = 0;
        while ($count < $key_len + $iv_len) {
            $data = $password;
            if ($i > 0) {
                $data = $m[$i - 1] . $password;
            }
            $d = md5($data, true);
            $m[] = $d;
            $count += strlen($d);
            $i += 1;
        }
        $ms = '';
        foreach ($m as $buf) {
            $ms .= $buf;
        }
        $key = substr($ms, 0, $key_len);
        $iv = substr($ms, $key_len, $key_len + $iv_len);
        $this->_bytesToKeyResults[$password] = array($key, $iv);
        return array($key, $iv);
    }

    protected function getCipherLen($method)
    {
        $method = strtolower($method);
        return isset(self::$_methodSupported[$method]) ? self::$_methodSupported[$method] : null;
    }
}

/**
 * Class Encipher
 */
class Encipher
{

    protected $_algorithm;

    protected $_key;

    protected $_iv;

    protected $_tail;

    protected $_ivLength;

    public function __construct($algorithm, $key, $iv)
    {
        $this->_algorithm = $algorithm;
        $this->_key = $key;
        $this->_iv = $iv;
        $this->_ivLength = openssl_cipher_iv_length($algorithm);
    }

    public function update($data)
    {
        if (strlen($data) == 0)
            return '';
        $tl = strlen($this->_tail);
        if ($tl)
            $data = $this->_tail . $data;
        $b = openssl_encrypt($data, $this->_algorithm, $this->_key, OPENSSL_RAW_DATA, $this->_iv);
        $result = substr($b, $tl);
        $dataLength = strlen($data);
        $mod = $dataLength % $this->_ivLength;
        if ($dataLength >= $this->_ivLength) {
            $iPos = -($mod + $this->_ivLength);
            $this->_iv = substr($b, $iPos, $this->_ivLength);
        }
        $this->_tail = $mod != 0 ? substr($data, -$mod) : '';
        return $result;
    }
}

class Decipher extends Encipher
{

    public function update($data)
    {
        if (strlen($data) == 0)
            return '';
        $tl = strlen($this->_tail);
        if ($tl)
            $data = $this->_tail . $data;
        $b = openssl_decrypt($data, $this->_algorithm, $this->_key, OPENSSL_RAW_DATA, $this->_iv);
        $result = substr($b, $tl);
        $dataLength = strlen($data);
        $mod = $dataLength % $this->_ivLength;
        if ($dataLength >= $this->_ivLength) {
            $iPos = -($mod + $this->_ivLength);
            $this->_iv = substr($data, $iPos, $this->_ivLength);
        }
        $this->_tail = $mod != 0 ? substr($data, -$mod) : '';
        return $result;
    }
}

function merge_sort($array, $comparison)
{
    if (count($array) < 2) {
        return $array;
    }
    $middle = ceil(count($array) / 2);
    return merge(merge_sort(slice($array, 0, $middle), $comparison), merge_sort(slice($array, $middle), $comparison), $comparison);
}

function slice($table, $start, $end = null)
{
    $table = array_values($table);
    if ($end) {
        return array_slice($table, $start, $end);
    } else {
        return array_slice($table, $start);
    }
}

function merge($left, $right, $comparison)
{
    $result = array();
    while ((count($left) > 0) && (count($right) > 0)) {
        if (call_user_func($comparison, $left[0], $right[0]) <= 0) {
            $result[] = array_shift($left);
        } else {
            $result[] = array_shift($right);
        }
    }
    while (count($left) > 0) {
        $result[] = array_shift($left);
    }
    while (count($right) > 0) {
        $result[] = array_shift($right);
    }
    return $result;
}

class Logger
{

    /**
     * debug信息
     * @param string $info
     */
    public static function debug($info)
    {
        if (self::isDebug()) {

            $now = date('Y-m-d H:i:s', time());
            echo '[' . $now . ']' . $info . PHP_EOL;
        }
    }

    /**
     * debug信息
     * @param string $info
     */
    public static function info($info)
    {
        $now = date('Y-m-d H:i:s', time());
        echo '[' . $now . '] ' . $info . PHP_EOL;
    }

    /**
     *
     */
    protected static function isDebug()
    {
        return defined('DEBUG') ? DEBUG : false;
    }
}

class Sock5
{

    const ADDRTYPE_IPV4 = 1;

    const ADDRTYPE_IPV6 = 4;

    const ADDRTYPE_HOST = 3;

    /**
     * 解析shadowsocks客户端发来的socket5头部数据
     * @param string $buffer
     * @return array
     */
    public static function parseHeader($buffer)
    {
        $addr_type = ord($buffer[0]);
        switch ($addr_type) {
            case self::ADDRTYPE_IPV4:
                $dest_addr = ord($buffer[1]) . '.' . ord($buffer[2]) . '.' . ord($buffer[3]) . '.' . ord($buffer[4]);
                $port_data = unpack('n', substr($buffer, 5, 2));
                $dest_port = $port_data[1];
                $header_length = 7;
                break;
            case self::ADDRTYPE_HOST:
                $addrlen = ord($buffer[1]);
                $dest_addr = substr($buffer, 2, $addrlen);
                $port_data = unpack('n', substr($buffer, 2 + $addrlen, 2));
                $dest_port = $port_data[1];
                $header_length = $addrlen + 4;
                break;
            case self::ADDRTYPE_IPV6:
                return [];
            default:
                return [];
        }
        return array('type' => $addr_type, 'addr' => $dest_addr, 'port' => $dest_port, 'length' => $header_length);
    }
}

/*
开启OTA后包头格式
+------+---------------------+------------------+-----------+----------+
| ATYP | Destination Address | Destination Port | HMAC-SHA1 |   Data   |
+------+---------------------+------------------+-----------+----------+
|  1   |       Variable      |         2        |    10     | Variable |
+------+---------------------+------------------+-----------+----------+
开启OTA后 每个数据包格式
+----------+-----------+----------+----
| DATA.LEN | HMAC-SHA1 |   DATA   | ...
+----------+-----------+----------+----
|     2    |     10    | Variable | ...
+----------+-----------+----------+----
*/
function onetimeauth_gen($data, $key)
{
    $Sig = hash_hmac('sha1', $data, $key, true);
    return substr($Sig, 0, ONETIMEAUTH_BYTES);
}

class ShadowSocksServer
{
    protected $serv = array();
    // 前端
    protected $myClients;
    // logger
    protected $logger;
    // config
    protected $config;
    public function __construct()
    {
        $this->config = [
            'daemon' => true,
            'server' => '0.0.0.0',
            'server_port' => 444,
            'password' => 'yourpassword',
            'method' => 'aes-256-cfb',
            'ota_enable' => false
        ];
        $this->serv = new \Swoole\Server($this->config['server'], $this->config['server_port'], SWOOLE_PROCESS, SWOOLE_SOCK_TCP);
        $this->serv->addlistener($this->config['server'], $this->config['server_port'], SWOOLE_SOCK_UDP);

        $this->serv->on('connect', [$this, 'onConnect']);
        $this->serv->on('receive', [$this, 'onReceive']);
        $this->serv->on('close', [$this, 'onClose']);

        //监听udp数据发送事件
        $this->serv->on('Packet', [$this, 'onPacket']);

        $this->logger = new Logger();
    }

    public function onWorkerStart($serv, $worker_id)
    {
        //每6小时清空一次dns缓存
        swoole_timer_tick(21600000, function () {
            swoole_clear_dns_cache();
            $this->logger->info('Flush dnsCache');
        });
    }

    //缓冲区控制
    public function onBufferFull($serv, $fd)
    {
        $this->myClients[$fd]['overflowed'] = true;
        $this->logger->info("server is overflowed connection[fd=$fd]");
    }

    public function onBufferEmpty($serv, $fd)
    {
        $this->myClients[$fd]['overflowed'] = false;
    }

    /**
     * @param \Swoole\Server $serv
     * @param $fd
     */
    public function onConnect($serv, $fd)
    {
        // 设置当前连接的状态为 STAGE_INIT ，初始状态
        if (!isset($this->myClients[$fd])) {
            $this->myClients[$fd]['stage'] = STAGE_INIT;
        }

        $this->myClients[$fd]['info'] = $serv->connection_info($fd);//server_port
        $this->myClients[$fd]['encryptor'] = new Encryptor($this->config['password'], $this->config['method']);
        //初始化各属性
        $this->myClients[$fd]['splQueue'] = new SplQueue();
        //判断缓冲区是否已满
        $this->myClients[$fd]['overflowed'] = false;

        //$this->myClients[$fd]['ota_enable'] = $this->config['ota_enable'];
        $this->myClients[$fd]['_ota_chunk_idx'] = 0;
        $this->myClients[$fd]['_ota_len'] = 0;
        $this->myClients[$fd]['_ota_buff_head'] = b"";
        $this->myClients[$fd]['_ota_buff_data'] = b"";
    }

    /**
     * @param \Swoole\Server $serv
     * @param $fd
     * @param $from_id
     * @param $data
     * @return void
     */
    public function onReceive($serv, $fd, $from_id, $data)
    {
        $clientFd = $this->myClients[$fd];
        /** @var Encryptor $encryptor */
        $encryptor = $clientFd['encryptor']??null;
        if (!$encryptor) {
            return;
        }
        // 先解密数据
        $data = $encryptor->decrypt($data);

        $remote_port = $clientFd['info']['remote_port'];
        $remote_ip = $clientFd['info']['remote_ip'];
        $server_port = $clientFd['info']['server_port'];
        switch ($clientFd['stage']) {
            // 如果不是STAGE_STREAM，则尝试解析实际的请求地址及端口
            case STAGE_INIT:
            case STAGE_ADDR:
                // 解析socket5头
                $header = $this->parse_socket5_header($data);
                // 解析头部出错，则关闭连接
                if (!$header) {
                    $this->logger->info("parse header error maybe wrong password {$remote_ip}:{$remote_port} server port:{$server_port}");
                    $serv->close($fd);
                    return;
                }
                //头部长度
                $header_len = $header[3];
                $clientFd['ota_enable'] = $header[4];
                //头部OTA判断
                if ($clientFd['ota_enable']) {
                    if (strlen($data) < ($header_len + ONETIMEAUTH_BYTES)) {
                        $this->logger->info("TCP OTA header is too short server port:{$server_port}");
                        $serv->close($fd);
                        return;
                    }
                    //$offset	= $header_len + ONETIMEAUTH_BYTES;
                    //客户端发过来的头部hash值
                    $_hash = substr($data, $header_len, ONETIMEAUTH_BYTES);
                    $_data = substr($data, 0, $header_len);

                    $_key = $encryptor->_key;
                    $key = $encryptor->_decipherIv . $_key;
                    //验证OTA 头部hash值
                    $gen = onetimeauth_gen($_data, $key);
                    if ($gen != $_hash) {
                        $this->logger->info("TCP OTA header fail server port:{$server_port}");
                        $serv->close($fd);
                        return;
                    }
                    $header_len += ONETIMEAUTH_BYTES;
                    //数据部分OTA判断
                    //$data = substr($data,$header_len);
                }

                //尚未建立连接
                if (!isset($clientFd['clientSocket'])) {
                    $clientFd['stage'] = STAGE_CONNECTING;
                    //连接到后台服务器
                    $clientSocket = new \Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_ASYNC);
                    $clientSocket->closing = false;
                    $clientSocket->on('connect', function (swoole_client $clientSocket) use ($data, $fd, $header_len) {

                        $clientFd['clientSocket'] = $clientSocket;
                        // shadowsocks客户端第一次发来的数据超过头部，则要把头部后面的数据发给远程服务端
                        if (strlen($data) > $header_len) {
                            $this->writeToSock($fd, substr($data, $header_len));
                        }
                        /** @var SplQueue $splQueue */
                        $splQueue = $clientFd['splQueue']??null;
                        $count = isset($splQueue) ? count($splQueue) : 0;
                        for ($i = 0; $i < $count; $i++) {//读取队列
                            $v = $splQueue->shift();
                            $this->writeToSock($fd, $v);
                        }
                        $clientFd['stage'] = STAGE_STREAM;
                    });
                    $clientSocket->on('error', function (swoole_client $clientSocket) use ($fd) {
                        $this->serv->close($fd);
                    });
                    $clientSocket->on('close', function (swoole_client $clientSocket) use ($fd) {
                        if (!$clientSocket->closing) {
                            $clientSocket->closing = true;
                            $this->serv->close($fd);
                        }
                        if (isset($clientFd)) unset($clientFd);
                        //$this->logger->info( "client {$fd} closed memory_get_usage:" . memory_get_usage() );
                    });
                    $clientSocket->on('receive', function (swoole_client $clientSocket, $_data) use ($encryptor, $fd) {
                        $_data = $encryptor->encrypt($_data);
                        if (isset($clientFd['overflowed']) && $clientFd['overflowed'] == false) {
                            $res = $this->serv->send($fd, $_data);
                            if ($res) {
                                //todo流量统计
                            } else {
                                $errCode = $this->serv->getLastError();
                                if (1008 == $errCode) {//缓存区已满
                                } else {
                                    $this->logger->info("send uncatched errCode:$errCode");
                                }
                            }
                        }

                    });

                    if ($header[0] == ADDRTYPE_HOST) {
                        swoole_async_dns_lookup($header[1], function ($host, $ip) use ($clientFd, $header, $clientSocket, $fd) {
                            $remote_ip = $clientFd['info']['remote_ip'];
                            $remote_port = $clientFd['info']['remote_port'];
                            $server_port = $clientFd['info']['server_port'];
                            $ota = $header[4] ? 'OTA' : '';
                            $this->logger->info(
                                "TCP {$ota} connecting {$host}:{$header[2]} from {$remote_ip}:{$remote_port} server port:{$server_port} @line:" . __LINE__
                            );
                            if ($ip && 0 < $header[2] && $server_port) {
                                $clientSocket->connect($ip, $header[2]);
                            }
                            $clientFd['stage'] = STAGE_CONNECTING;
                        });
                    } elseif ($header[0] == ADDRTYPE_IPV4) {
                        $ota = $header[4] ? 'OTA' : '';
                        $this->logger->info(
                            "TCP {$ota} connecting {$header[1]}:{$header[2]} from {$remote_ip}:{$remote_port} server port:{$server_port} @Line:" . __LINE__
                        );
                        $clientSocket->connect($header[1], $header[2]);
                        $clientFd['stage'] = STAGE_CONNECTING;
                    }

                }
                break;
            case STAGE_CONNECTING:
                $clientFd['splQueue']->push($data);
                break;
            case STAGE_STREAM:
                if (isset($clientFd['clientSocket'])) {
                    $this->writeToSock($fd, $data);
                }
                break;
            default:
                break;
        }
    }

    public function writeToSock($fd, $data)
    {
        if ($this->myClients[$fd]['ota_enable']) {
            $this->_ota_chunk_data($fd, $data);
        } else {
            $cli = $this->getClient($fd);
            $cli and $cli->send($data);
        }
    }

    /*
    UDP 代理部分的规则 by @Zac
    $clientInfo
    Array
    (
        [server_socket] => 4
        [address] => 127.0.0.1
        [port] => 55376
    )
    */
    /**
     * @param \Swoole\Server $serv
     * @param $data
     * @param $clientInfo
     */
    public function onPacket($serv, $data, $clientInfo)
    {
        $encryptor = new Encryptor($this->config['password'], $this->config['method']);

        //计算当前server数据
        $info = $serv->getClientInfo(ip2long($clientInfo['address']), ($clientInfo['server_socket'] << 16) + $clientInfo['port']);
        $server_port = $info['server_port'];

        $this->logger->info("UDP,server_socket :{$clientInfo['server_socket']}");
        $data = $encryptor->decrypt($data);
        if (!$data) {
            $this->logger->info("UDP handle_server: data is empty after decrypt server port:{$server_port}");
            return;
        }
        $header = $this->parse_socket5_header($data);
        // 解析头部出错，则关闭连接
        if (!$header) {
            $this->logger->info("parse UDP header error maybe wrong password {$clientInfo['address']}:{$clientInfo['port']} server port:{$server_port}");
            return;
        }
        //addrtype, dest_addr, dest_port, header_length, ota_enable= header_result
        $clientSocket = new swoole_client(SWOOLE_SOCK_UDP, SWOOLE_SOCK_ASYNC);
        //头部长度
        $header_len = $header[3];
        if ($header[4]) {
            if (strlen($data) < ($header_len + ONETIMEAUTH_BYTES)) {
                $this->logger->info("UDP OTA header is too short server port:{$server_port}");
                return;
            }
            $_hash = substr($data, -ONETIMEAUTH_BYTES);
            $data = substr($data, 0, -ONETIMEAUTH_BYTES);

            $_key = $encryptor->_key;
            $key = $encryptor->_decipherIv . $_key;
            //验证OTA 头部hash值
            $gen = onetimeauth_gen($data, $key);
            if ($gen != $_hash) {
                $this->logger->info("UDP OTA header fail  server port:{$server_port}");
                return;
            }
        }
        $clientSocket->on('connect', function (swoole_client $clientSocket) use ($data, $header_len) {
            if (strlen($data) > $header_len) {
                $clientSocket->send(substr($data, $header_len));
            }
        });


        $clientSocket->on('receive', function (swoole_client $clientSocket, $_data) use ($encryptor, $serv, $clientInfo, $header) {
            //$errCode = $serv->getLastError(); if (1008 == $errCode)//缓存区已满
            //先判断 send或者 push的返回是否为false, 然后调用 getLastError得到错误码，进行对应的处理逻辑
            try {
                $_header = $this->pack_header($header[1], $header[0], $header[2]);
                $_data = $encryptor->encrypt($_header . $_data);
                $serv->sendto($clientInfo['address'], $clientInfo['port'], $_data, $clientInfo['server_socket']);
            } catch (Exception $e) {
                //var_dump($e);
            }
        });
        if ($header[0] == ADDRTYPE_HOST) {
            swoole_async_dns_lookup($header[1], function ($host, $ip) use (&$header, $clientSocket, $clientInfo, $server_port) {
                $ota = $header[4] ? 'OTA' : '';
                $this->logger->info(
                    "UDP {$ota} connecting {$host}:{$header[2]} from {$clientInfo['address']}:{$clientInfo['port']} server port:{$server_port}"
                );
                $header[1] = $ip;
                $clientSocket->connect($ip, $header[2]);
            });
        } elseif ($header[0] == ADDRTYPE_IPV4) {
            $ota = $header[4] ? 'OTA' : '';
            $this->logger->info(
                "UDP {$ota} connecting {$header[1]}:{$header[2]} from {$clientInfo['address']}:{$clientInfo['port']} server port:{$server_port}"
            );
            $clientSocket->connect($header[1], $header[2]);
        } else {

        }
    }

    /**
     * UDP 部分 返回客户端 头部数据 by @Zac
     * 生成UDP header 它这里给返回解析出来的域名貌似给udp dns域名解析用的
     * @param $addr
     * @param $addr_type
     * @param $port
     * @return string
     */
    public function pack_header($addr, $addr_type, $port)
    {
        switch ($addr_type) {
            case ADDRTYPE_IPV4:
                $header = b"\x01" . inet_pton($addr);
                break;
            case ADDRTYPE_IPV6:
                $header = b"\x04" . inet_pton($addr);
                break;
            case ADDRTYPE_HOST:
                if (strlen($addr) > 255) {
                    $addr = substr($addr, 0, 255);
                }
                $header = b"\x03" . chr(strlen($addr)) . $addr;
                break;
            default:
                return '';
        }
        return $header . pack('n', $port);
    }

    /**
     * @param $fd
     * @return false|\Swoole\Client
     */
    private function getClient($fd)
    {
        return $this->myClients[$fd]['clientSocket']??false;
    }

    function onClose($serv, $fd, $from_id)
    {
        $clientSocket = $this->getClient($fd);
        if ($clientSocket) {
            if ($clientSocket->closing === false) {
                $clientSocket->closing = true;
                $clientSocket->close();
            }
            if (isset($this->myClients[$fd])) unset($this->myClients[$fd]);
        }
    }

    public function start()
    {
        $default = [
            'daemonize' => $this->config['daemon'],
            'timeout' => 60,
            'dispatch_mode' => 2,
            'log_file' => './swoole.log'
        ];
        $this->serv->set($default);
        $this->serv->start();
    }

    /**
     * 解析shadowsocks客户端发来的socket5头部数据
     * @param string $buffer
     * @return array|bool
     */
    public function parse_socket5_header(string $buffer)
    {
        $addr_type = ord($buffer[0]);
        $ota_enable = false;
        if ($this->config['ota_enable'] || ($addr_type & ADDRTYPE_AUTH) == ADDRTYPE_AUTH) {
            $ota_enable = true;
            $addr_type = $addr_type ^ ADDRTYPE_AUTH;//把第四位值空0b00010000==>0b00000000
        }
        switch ($addr_type) {
            case ADDRTYPE_IPV4:
                $dest_addr = ord($buffer[1]) . '.' . ord($buffer[2]) . '.' . ord($buffer[3]) . '.' . ord($buffer[4]);
                $port_data = unpack('n', substr($buffer, 5, 2));
                $dest_port = $port_data[1];
                $header_length = 7;
                break;
            case ADDRTYPE_HOST:
                $addrlen = ord($buffer[1]);
                $dest_addr = substr($buffer, 2, $addrlen);
                $port_data = unpack('n', substr($buffer, 2 + $addrlen, 2));
                $dest_port = $port_data[1];
                $header_length = $addrlen + 4;
                break;
            case ADDRTYPE_IPV6:
                $this->logger->info("todo ipv6 not support yet");
                return false;
            default:
                $this->logger->info("unsupported addrtype $addr_type");
                return false;
        }
        //将是否是 OTA 一并返回
        return array($addr_type, $dest_addr, $dest_port, $header_length, $ota_enable);
    }

    /*
        ss OTA 功能拆包部分 by @Zac
    */
    function _ota_chunk_data($fd, $data)
    {
        //tcp 是流式传输，接收到的数据包可能不是一个完整的chunk 不能以strlen来判断长度然后直接return
        $server_port = $this->myClients[$fd]['info']['server_port'];
        while (strlen($data) > 0) {
            if ($this->myClients[$fd]['_ota_len'] == 0) {
                $_ota_buff_head_len = strlen($this->myClients[$fd]['_ota_buff_head']);    //已缓存的头部长度
                $left_ota_buff_head = ONETIMEAUTH_CHUNK_BYTES - $_ota_buff_head_len;    //还需缓存的头部长度
                $this->myClients[$fd]['_ota_buff_head'] .= substr($data, 0, $left_ota_buff_head);

                $data = substr($data, $left_ota_buff_head);
                //缓存到规定长度后开始解析头部
                if (strlen($this->myClients[$fd]['_ota_buff_head']) === ONETIMEAUTH_CHUNK_BYTES) {
                    $data_len = substr($this->myClients[$fd]['_ota_buff_head'], 0, ONETIMEAUTH_CHUNK_DATA_LEN);
                    //理论上一个完整OTA加密包的长度
                    $this->myClients[$fd]['_ota_len'] = unpack('n', $data_len)[1];
                }
            }

            $buffed_data_len = strlen($this->myClients[$fd]['_ota_buff_data']);//已获取数据长度
            $left_buffed_data_len = $this->myClients[$fd]['_ota_len'] - $buffed_data_len;//还应该获取的数据长度

            $this->myClients[$fd]['_ota_buff_data'] .= substr($data, 0, $left_buffed_data_len);
            $data = substr($data, $left_buffed_data_len);
            //接收到了一个完整的包，开始OTA包校验
            if (strlen($this->myClients[$fd]['_ota_buff_data']) == $this->myClients[$fd]['_ota_len']) {
                $_hash = substr($this->myClients[$fd]['_ota_buff_head'], ONETIMEAUTH_CHUNK_DATA_LEN);

                $_data = $this->myClients[$fd]['_ota_buff_data'];
                $index = pack('N', $this->myClients[$fd]['_ota_chunk_idx']);
                $key = $this->myClients[$fd]['encryptor']->_decipherIv . $index;
                $gen = onetimeauth_gen($_data, $key);
                if ($gen == $_hash) {
                    //将当前通过校验的数据包转发出去,同时编号+1
                    /** @var \Swoole\Client $clientSocket */
                    $clientSocket = $this->myClients[$fd]['clientSocket'];
                    $clientSocket->send($this->myClients[$fd]['_ota_buff_data']);
                    $this->myClients[$fd]['_ota_chunk_idx'] += 1;
                    //$this->logger->info("TCP OTA chunk ok ok ok ok! server port:{$server_port}");
                } else {
                    $this->logger->info("TCP OTA fail, drop chunk ! server port:{$server_port}");
                }
                $this->myClients[$fd]['_ota_buff_head'] = b"";
                $this->myClients[$fd]['_ota_buff_data'] = b"";
                $this->myClients[$fd]['_ota_len'] = 0;
            }
        }
    }
}

$s = new ShadowSocksServer();
$s->start();
